2 GPU运算 自动微分 数据加载与处理
1 GPU 运算
| 函数 | 说明 | 用例 |
|---|---|---|
torch.cuda_is_available() |
检验 CUDA 是否可用 | |
torch.cuda.device_count() |
检查可用 GPU 数量 | |
torch.device() |
cpu: 使用 CPU 设备; cuda: 只有一块 GPU 时启用它;cuda:0: 使用第一块 GPU; 以此类推 |
|
.to(device) |
将张量转移到某个设备[1] | gpu = torch.device('cuda:0')a_cpu = torch.tensor([1])a_gpu = a_cpu.to(gpu) |
del |
删除指定的张量 | del a_gpu |
torch.cuda.empty_cache() |
清空缓存 |
2 自动微分
自动微分 (Aotograd) 是 PyTorch 最重要的组件! 它能自动完成反向传播的链式求导.
2.1 使用
考虑下面的计算关系
x = torch.tensor([torch.pi/3, requires_grad=True]) # 启用梯度追踪
y = torch.tensor([1.0]) # 不需要求导, 默认不启用
z = torch.cos(x) / (1 + y)
u = torch.log(2 * z + 1)
L = u * x + y
L.backward() # 反向传播, 计算梯度
print(x.grad)
多次调用
.backward()会导致梯度累加, 因此每次更新参数需要手动清零梯度.
我们可以调用optimizer.zero_grad()来一次性清空, 或者x.grad.zero_()来手动清空.
2.2 detach
如果我们想复制一个张量, 并不让它追踪梯度、影响计算图里主张量的梯度计算, 需要用 detach(). 它会共享数据, 但剥离计算图. 通常我们仅仅是为了读取中间结果.
a = torch.tensor([1.0], requires_grad=True)
b = torch.tensor([2.0], requires_grad=True)
c = a * b
d = c.detach() # 创建剥离计算图的d, 共享数据[2.0], 但不参与计算图
e = d * 3 # 无法进行 e.backward()
d[0] = 10.0
print(c) # tensor([10.]), 数据也会同步改变
f = c ** 2
f.backward() # 原始计算图依然完整
2.3 no_grad
在 with torch.no_grad(): 内, 创建的张量都不会参与梯度计算. 用在验证集/测试集评估模型性能时.
x = torch.tensor([2.0], requires_grad=True)
with torch.no_grad():
z = x ** 3
print(z.requires_grad) # False
t = x ** 2
print(t.requires_grad) # True
3 数据加载与处理
3.1 DataSet
PyTorch 硬性规定要实现 __len__ (返回数据集的大小) 和 __getitem__ 索引 (必须返回 样本 标签 格式)
from torch.utils.data import Dataset
class MyDataset(Dataset):
def __init__(self, data, labels):
self.data = data
self.labels = labels
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
"""根据索引idx返回样本及其标签"""
sample = self.data[idx]
label = self.labels[idx]
return sample, label
# 用法: 准备data的tensor和label的tensor
dataset = MyDataset(mydata, mylabel)
print(dataset[2])
# (tensor([...]), tensor(..)) 是一个元组
3.2 DataLoader
相比 dataset, 实现批量读取. 具体来说它可以做三件事
- 自动分批 (Batching): 由
batch_size控制. - 数据打乱 (Shuffling): 每个 epoch 开始前, 打乱数据集的顺序. 由
shuffle控制 - 多进程加速 (Multiprocessing): 启动多个子进程加载数据. 由
num_workers控制
from torch.utils.data import Dataset, DataLoader
class MyDataset(Dataset):
# 和上面一样
data = torch.randn(200, 4)
labels = torch.randint(0, 2, (200,))
dataset = Dataset(data, labels)
# 创建dataloader
dataloader = DataLoader(
dataset, #dataset实例
batch_size=5, #每批5个样本
shuffle, #每个epoch打乱数据
num_workers=4, #4个子进程加载数据
drop_last=True, #丢弃最后不足batch_size的批次
pin_memory=True,#有GPU时建议True, 加速CPU->GPU传输
)
# 用法: for循环遍历
for batch_data, batch_labels in dataloader:
print(batch_data, batch_data.shape)
print(batch_labels, batch_labels.shape)
在 Windows 中, 一般会把 DataLoader 相关代码放入
if __name__ == '__main__'中, 防止无限递归创建子进程.if __name__ == '__main__': dataset = MyDataset(data, labels) dataloader = DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4) for batch_data, batch_labels in dataloader: pass
3.3 划分数据集
random_split(dataset, lengths) 可用于将数据集划分为训练集和测试集.
from torch.utils.data import random_split
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_set, val_set = random_split(dataset, [train_size, val_size])
random_split只记录原始数据集的索引, 不会占用额外内存.- 划分是随机的. 如果希望结果重复, 可以使用
torch.manual_seed()(参见 这里)
然后我们为训练集、验证集创建 dataloader 即可 (验证集无需 shuffle).
为了提高读写效率, 尽量在数据初始化时就放在 GPU 上, 到输出结果、保存模型时才放回 CPU ↩︎